﻿namespace ClassyBlog.Web.Specs.Unit
{
    using System;
    using System.Linq;
    using System.Web.Mvc;

    using Machine.Specifications;
    using NSubstitute;

    using DataAccess;
    using DomainObjects;
    using ExtensionMethods;

    [Subject(typeof(PostsController))]
    public class when_creating_posts_controller_with_null_repository
    {
        static Exception exception;

        Because of = () =>
            exception = Catch.Exception(() => new PostsController(null));

        It should_throw_exception = () =>
            exception.ShouldBeOfType<ArgumentNullException>();
    }

    public abstract class PostsControllerSpec
    {
        protected static Blog blog;
        protected static IPostRepository repository;
        protected static PostsController controller;

        Establish context = () =>
        {
            blog = ObjectMother.CreateBlog();
            blog.Merge(new { PostsPerPage = 10 });

            repository = Substitute.For<IPostRepository>();
            controller = new PostsController(repository);

            controller.SetBlog(blog);
        };
    }

    [Behaviors]
    public class MultiplePostsResult
    {
        protected static Blog blog;
        protected static ViewResult result;
        protected static PagedList<Post> genericModel;

        It should_render_default_view = () => result.ViewName.ShouldBeEmpty();

        It should_set_correct_page = () =>
            genericModel.CurrentPage.ShouldEqual(1);

        It should_set_correct_total = () =>
            genericModel.Total.ShouldEqual(10);

        It should_set_correct_page_size = () =>
            genericModel.PageSize.ShouldEqual(blog.PostsPerPage);

        It should_set_correct_list = () =>
            genericModel.List.ShouldNotBeEmpty();
    }

    [Subject(typeof(PostsController))]
    public class when_listing_all_posts : PostsControllerSpec
    {
        protected static ViewResult result;
        protected static PagedList<Post> genericModel;

        Establish context = () =>
            repository.All().Returns(ObjectMother.CreatePosts(10));

        Because of = () =>
        {
            result = (ViewResult)controller.Index(1, null);
            genericModel = (PagedList<Post>)result.Model;
        };

        Behaves_like<MultiplePostsResult> multiplePosts;
    }

    [Subject(typeof(PostsController))]
    public class when_listing_posts_by_dates : PostsControllerSpec
    {
        protected static ArchivedDates archivedDates;
        protected static ViewResult result;
        protected static ArchivedPostList viewModel;
        protected static PagedList<Post> genericModel;

        Establish context = () =>
        {
            archivedDates = new ArchivedDates(2011, 5, null);

            var year = archivedDates.Year ?? 0;
            var month = archivedDates.Month ?? 0;

            var posts = ObjectMother.CreatePosts(100).ToList();

            posts.EachWithIndex((p, i) =>
            {
                if (i < 10)
                {
                    var day = new Random().Next(
                        1, DateTime.DaysInMonth(year, month));

                    p.PublishedAt = new DateTime(year, month, day);
                }
            });

            repository.All().Returns(posts);
        };

        Because of = () =>
        {
            result = (ViewResult)controller.Index(1, archivedDates);

            viewModel = (ArchivedPostList)result.Model;
            genericModel = viewModel;
        };

        It should_set_same_archive_dates = () =>
            viewModel.ArchivedDates.ShouldBeTheSameAs(archivedDates);

        Behaves_like<MultiplePostsResult> multiplePosts;
    }

    [Subject(typeof(PostsController))]
    public class when_listing_empty_posts_for_default_listing :
        PostsControllerSpec
    {
        static HttpNotFoundResult result;

        Establish context = () =>
            repository.All().Returns(Enumerable.Empty<Post>());

        Because of = () => result =
            controller.Index(1, null) as HttpNotFoundResult;

        It should_throw_404 = () => result.ShouldNotBeNull();
    }

    [Subject(typeof(PostsController))]
    public class when_listing_posts_by_tag : PostsControllerSpec
    {
        protected static ViewResult result;
        protected static TaggedPostList viewModel;
        protected static PagedList<Post> genericModel;
        static Tag tag;

        Establish context = () =>
        {
            tag = ObjectMother.CreateTag();
            var posts = ObjectMother.CreatePosts(100).ToList();

            posts.EachWithIndex((p, i) =>
            {
                if (i < 10)
                {
                    p.Tags.Add(tag);
                }
            });

            repository.All().Returns(posts);
        };

        Because of = () =>
        {
            result = (ViewResult)controller.Tagged(1, tag.Slug);
            viewModel = (TaggedPostList)result.Model;
            genericModel = viewModel;
        };

        It should_set_same_tag = () =>
            viewModel.Tag.Slug.ShouldEqual(tag.Slug);

        Behaves_like<MultiplePostsResult> multiplePosts;
    }

    [Subject(typeof(PostsController))]
    public class when_listing_empty_posts_by_tag : PostsControllerSpec
    {
        static HttpNotFoundResult result;

        Establish context = () =>
            repository.All().Returns(Enumerable.Empty<Post>());

        Because of = () =>
            result = controller.Tagged(1, "foo-bar") as HttpNotFoundResult;

        It should_throw_404 = () => result.ShouldNotBeNull();
    }

    [Subject(typeof(PostsController))]
    public class when_showing_non_existent_post : PostsControllerSpec
    {
        static HttpNotFoundResult result;

        Establish context = () =>
            repository.FindOne(Arg.Any<string>()).Returns((Post)null);

        Because of = () =>
            result = controller.Show("foo") as HttpNotFoundResult;

        It should_throw_404 = () => result.ShouldNotBeNull();
    }

    [Subject(typeof(PostsController))]
    public class when_showing_existent_post : PostsControllerSpec
    {
        static Post post;
        static ViewResult result;

        Establish context = () =>
        {
            post = ObjectMother.CreatePost();
            repository.FindOne(Arg.Any<string>()).Returns(post);
        };

        Because of = () =>
            result = controller.Show("hello-world") as ViewResult;

        It should_return_view = () => result.ShouldNotBeNull();

        It should_set_post_as_view_model = () =>
            result.Model.To<ItemModel<Post>>().Item.ShouldBeTheSameAs(post);
    }
}